<?php
/**
 * Simple PHP Contact Form
 *
 * This file is part of the Simple PHP Contact Form which is subject to the New
 * BSD License {@see LICENSE} which you would be advised to read before using,
 * modifying or redistributing this software.
 *
 * PHP version 5.2
 *
 * @category  Simple-PHP-Contact-Form
 * @package   Application_Controllers_Helpers
 * @author    jah <jah@jahboite.co.uk>
 * @copyright 2010 jah <jah@jahboite.co.uk>
 * @license   New BSD License {@see LICENSE}
 * @version   SVN: $Id$
 * @link      http://code.google.com/p/simple-php-contact-form/
 */


/**
 * Action Helper for sending mail.
 *
 * @category  Simple-PHP-Contact-Form
 * @package   Application_Controllers_Helpers
 * @author    jah <jah@jahboite.co.uk>
 * @copyright 2010 jah <jah@jahboite.co.uk>
 * @license   New BSD License {@see LICENSE}
 * @version   Release: @package_version@
 * @link      http://code.google.com/p/simple-php-contact-form/
 */
class App_Helper_SendMail extends Zend_Controller_Action_Helper_Abstract
{
    private $_rawVals  = null;
    private $_recipientConfig = null;
    private $_response = null;
    private $_auditor = null;


    /**
     * Initialises logging.
     *
     * @return null
     */
    public function __construct()
    {
        $this->_auditor = ESAPI::getAuditor('App_Helper_SendMail');
    }


    /**
     * Returns the response message.
     *
     * @return string Response message.
     */
    public function getResponse()
    {
        return $this->_response;
    }


    /**
     * Strategy pattern: call helper as broker method.
     *
     * @param array $values An array of name value pairs obtained from the
     *                      submitted form. {@see _compose}
     *
     * @return bool True if the request to the mail transport resulted in
     *              successful delivery or False otherwise.
     */
    public function direct($values)
    {
        return $this->request($values);
    }


    /**
     * Request the delivery of emial via the mail transport defined in the mail
     * configuration file.
     *
     * @param array $values An array of name value pairs obtained from the
     *                      submitted form. {@see _compose}
     *
     * @return bool True if the request to the mail transport resulted in
     *              successful delivery or False otherwise.
     */
    public function request($values)
    {
        $this->_rawVals = $values;
        $defaultTransport = null;

        // get the bootstrap, need to retrieve configs
        $front = Zend_Controller_Front::getInstance();
        $bs = $front->getParam('bootstrap');

        // get mail configuration
        $mailConfigLoc = $bs->getOption('mailconfigloc');
        $mailConfig = null;
        if (! empty($mailConfigLoc)) {
            $mailConfig = new Zend_Config_Ini(
                $mailConfigLoc, APPLICATION_ENV, true
            );
        }
        if ($mailConfig instanceof Zend_Config === false) {
            $mailConfig = new Zend_config(array(), true);
            $this->Auditor->warning(
                Auditor::SECURITY, false,
                'SendMail.request() cannot find mail configuration. Using defaults!'
            );
        }
        $transportType = 'sendmail';
        $transportOpts = null;
        if (isset($mailConfig->transport->type)) {
            $transportType = $mailConfig->transport->type;
            unset($mailConfig->transport->type);
            $transportOpts = $mailConfig->transport;
            unset($mailConfig->transport);
        }
        $mailResource = new Zend_Application_Resource_Mail($mailConfig);
        if ($transportType == 'smtp') {
            $defaultTransport = new Zend_Mail_Transport_Smtp(
                $transportOpts->host, $transportOpts->toArray()
            );
        } else if (ini_get('safe_mode')) {
            $defaultTransport = new Zend_Mail_Transport_Sendmail();
        } else {
            $defaultTransport = new Zend_Mail_Transport_Sendmail($transportOpts);
        }

        Zend_Mail::setDefaultTransport($defaultTransport);

        // get recipients configuration
        $recipientConfigLoc = $bs->getOption('recipientsconfigloc');
        $recipientConfig = null;
        if (! empty($recipientConfigLoc)) {
            $recipientConfig = new Zend_Config_Ini(
                $recipientConfigLoc, APPLICATION_ENV, false
            );
        }
        if ($recipientConfig instanceof Zend_Config) {
            $this->_recipientConfig = $recipientConfig;
        } else {
            $this->_auditor->warning(
                Auditor::SECURITY, false,
                'SendMail.request() cannot find any recipient data. Not sending mail!'
            );
        }

        // Compose Mail
        $mail = $this->_compose($mailConfig);

        // Make sure there's at least one recipient and bail if not.
        if (   $this->_recipientConfig === null
            || sizeof($this->_recipientConfig) == 0
        ) {
            $this->_response = 'No recipients of mail found!';
            $m = $mail->getBodyText()->getContent();
            $this->_auditor->info(
                Auditor::SECURITY, false,
                "No recipients of mail found. What do you want me to do with this: {$m}"
            );
            return false;
        }

        $cfg = null;
        if (sizeof($this->_recipientConfig) == 1) {

            $cfg = $this->_recipientConfig->current();

        } else {

            $i = 0;
            $limit = $this->_recipientConfig->count();
            while (++$i <= $limit) {
                $foo = $this->_recipientConfig->current()->display;
                if ($this->_rawVals['recipientMap'] === $this->_recipientConfig->current()->display) {
                    break;
                }
                $this->_recipientConfig->next();
            }
            $cfg = $this->_recipientConfig->current();

        }

        foreach ($cfg as $_ => $rcpt) {
            if ($_ == 'display') {
                continue;
            }
            $mail->addTo($rcpt->address, $rcpt->name);
        }

        try {

            $mail->send();

        } catch (Exception $e) {

            ESAPI::getIntrusionDetector()->addException($e);
            $this->_response = 'Call to mail.send threw an Exception: ' .
                $e->getMessage . '.';
            return false;

        }


        if ($transportType == 'sendmail') {

            $this->_response
                = 'mail.send did NOT throw and exception using sendmail transport.';
            $this->_auditor->info(Auditor::SECURITY, true, $this->_response);
            return true;

        } else {

            // get response and determine whether it represents success or failure.
            $connection = $defaultTransport->getConnection();
            $response = $connection->getResponse();
            $success = false;
            foreach ($response as $messageEntry) {
                $success = $this->_isSuccessMessage($messageEntry);
                $this->_auditor->info(
                    Auditor::SECURITY, $success,
                    "SMTP connection response: {$messageEntry}"
                );
            }
            if (sizeof($response, false) === 0) {
                // send got no reply...
                $this->_response = 'No Reply!';
                $this->_auditor->info(
                    Auditor::SECURITY, false, 'SMTP connection failed! No response.'
                );
                return $success;
            } else if (sizeof($response, false) === 1) {
                // got one response. good.
                $this->_response = array_shift($response);
                return $success;
            } else {
                $this->_auditor->info(
                    Auditor::SECURITY, false,
                    'SMTP connection returned multiple responses!'
                );
                $this->_response
                    = 'Multiple SMTP responses, don\'t know what\'s happening...';
                return false;
            }
        }
        // unreached
    }


    /**
     * _compose constructs the email headers and body from the values extracted
     * from the submitted form and according to the mail configuration.
     *
     * @param Zend_Congig $config The mail configuration.
     *
     * @return Zend_Mail Instance of a Zend_Mail object.
     */
    private function _compose($config)
    {
        $mail = new Zend_Mail('UTF-8');
        $mail->setHeaderEncoding(Zend_Mime::ENCODING_BASE64);

        // Set mail subject
        $subject = mb_ereg_replace(
            '\$host', $_SERVER['HTTP_HOST'], $config->subject, 'i'
        );
        $mail->setSubject($subject);

        if (array_key_exists('visitorName', $this->_rawVals)) {

            $this->visitorName = $this->_rawVals['visitorName'];
            unset($this->_rawVals['visitorName']);

        } else {

            $this->visitorName = $mail->getDefaultFrom['mail'];

        }
        if (array_key_exists('visitorEmail', $this->_rawVals)) {

            $this->visitorEmail = $this->_rawVals['visitorEmail'];
            unset($this->_rawVals['visitorEmail']);

        } else {

            $this->visitorEmail = $mail->getDefaultFrom['mail'];

        }
        if (array_key_exists('visitorMessage', $this->_rawVals)) {

            $this->visitorMessage = $this->_rawVals['visitorMessage'];
            unset($this->_rawVals['visitorMessage']);

        }

        if ($this->visitorEmail !== null && $this->visitorName !== null) {

            $mail->setFrom($this->visitorEmail, $this->visitorName);
            $mail->setReplyTo($this->visitorEmail, $this->visitorName);
        }

        $mail->setBodyText(
            $this->visitorMessage, 'UTF-8', Zend_Mime::ENCODING_BASE64
        );

        return $mail;
    }


    /**
     * Helper method which parses an SMTP response message to determine whether
     * it represents a successful delivery message.
     *
     * @param string $message SMTP Connection Response message.
     *
     * @return bool True if the message has a status code of 250 'Completed' or
     *              False otherwise.
     */
    private function _isSuccessMessage($message)
    {
        $canonical = ESAPI::getEncoder()->canonicalize($message);
        $detectedCharEnc = mb_detect_encoding($canonical);
        $potentialCode = mb_substr($canonical, 0, 3, $detectedCharEnc);
        $v_digits = new Zend_Validate_Digits();
        if ($v_digits->isValid($potentialCode) && $potentialCode == '250') {
            return true;
        }
        return false;
    }
}
